﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web;
using MetaCollection.ContentService.Configuration;
using MetaCollection.ContentService.WebDav;
using MetaCollection.Framework;
using MetaCollection.Framework.Model;

namespace MetaCollection.ContentService.Template
{
	public class ItemTemplate
	{
		/// <summary>
		/// The Template Mapping information stored in the collectionConfiguration section.
		/// </summary>
		private List<TemplateMap> m_TemplateMapping;

		/// <summary>
		/// Initializes the <see cref="Templating"/> class.
		/// </summary>
		internal ItemTemplate()
		{
			ContentConfigurationSection config = (ContentConfigurationSection)ConfigurationManager.GetSection("contentConfiguration");
			m_TemplateMapping = new List<TemplateMap>();
			foreach (TemplateMappingElement mapElement in config.Templating.Mapping)
			{
				TemplateMap map = new TemplateMap();
				if (!mapElement.Path.IsAbsoluteUri)
					map.Path = new Uri(HttpContext.Current.Server.MapPath(mapElement.Path.OriginalString));
				else
					map.Path = mapElement.Path;
				map.Name = mapElement.Name;
				map.ResourceProvider = mapElement.ResourceProvider;
				map.MimeType = mapElement.MimeType;
				map.OutputType = mapElement.OutputType;
				m_TemplateMapping.Add(map);
			}
		}

		/// <summary>
		/// Determines whether a [template is enabled] enabled on this item in [the specified context].
		/// </summary>
		/// <param name="context">The context to determine if the request requires a template.</param>
		/// <param name="item">The item to check for templating configuration.</param>
		/// <param name="itemContentType">The MimeType of the updated as required based on template configuration.</param>
		/// <param name="templateUri">The template URI to use.</param>
		/// <returns>
		/// 	<c>true</c> if a [template is enabled] for [the specified context]; otherwise, <c>false</c>.
		/// </returns>
		public bool IsTemplateEnabled(DavContext context, IItemInstance item, ref string itemContentType, ref Uri templateUri)
		{
			bool templateEnabled = false;
			if (context.Request.Translate)
			{
				// via an optional parameter (v) on the request that asks for a template by name in the map list from web.config.
				templateEnabled = TryGetRequestedTemplate(context.Request.Template, ref templateUri, ref itemContentType);
				if (!templateEnabled) // if we have no template yet try another way
					templateEnabled = TryGetAssignedTemplate(item, ref templateUri, ref itemContentType); // via a relationship that is of type "Template"
				if (!templateEnabled) // if we have no template yet try another way
					templateEnabled = TryGetMappedTemplate(item, ref templateUri, ref itemContentType); // via matches in the mapping list in the web.config file
			}
			return templateEnabled; // indicate if we found a template via any method
		}

		/// <summary>
		/// Tries the get requested template.
		/// </summary>
		/// <param name="requestedTemplate">The requested template.</param>
		/// <param name="templateUri">The template URI.</param>
		/// <param name="mimeType">Type of the MIME.</param>
		/// <returns></returns>
		private bool TryGetRequestedTemplate(string requestedTemplate, ref Uri templateUri, ref string mimeType)
		{
			// try and find a requested template, if that fails then look for an explicitly assigned template.
			if (!String.IsNullOrEmpty(requestedTemplate))
			{
				TemplateMap map = m_TemplateMapping.Find(m => m.Name.Equals(requestedTemplate, StringComparison.OrdinalIgnoreCase));
				if (map.Path != null)
				{
					templateUri = map.Path;
					mimeType = map.OutputType;
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Tries to get the assigned template
		/// </summary>
		/// <param name="item">The item to check for an assigned template.</param>
		/// <param name="templateUri">The template URI which will be updated if a template was found.</param>
		/// <param name="mimeType">MimeType of the output when the template is applied.</param>
		/// <returns>true if a template was found.</returns>
		private static bool TryGetAssignedTemplate(IItemInstance item, ref Uri templateUri, ref string mimeType)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			//if the given item has a template relationship, lets use that.
			IItemRelationship templateRelationship = item.Relationships.FirstOrDefault(p => p.RelationshipType == "Template");
			if (templateRelationship != null)
				templateUri = templateRelationship.TargetItem;
			return templateRelationship != null;
		}

		/// <summary>
		/// Tries to get a mapped template for this item from the configured template mapping.
		/// </summary>
		/// <param name="item">The item to look for a template for.</param>
		/// <param name="templateUri">The template URI to update if found.</param>
		/// <param name="mimeType">MimeType of the Item</param>
		/// <returns>true of a template mapping was found</returns>
		private bool TryGetMappedTemplate(IItemInstance item, ref Uri templateUri, ref string mimeType)
		{
			if (item == null)
				throw new ArgumentNullException("item");
			TemplateMap map = m_TemplateMapping.Find(m => m.MimeType.Equals(item.MimeType, StringComparison.OrdinalIgnoreCase) && m.ResourceProvider.Equals(item.Specification));
			if (map.Path != null)
			{
				templateUri = map.Path;
				mimeType = map.OutputType;
			}
			return (map.Path != null);
		}

		/// <summary>
		/// Renders the item with template.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="templateUri">The template URI.</param>
		/// <returns></returns>
		public static TextReader RenderItemWithTemplate(DavContext context, IRequestHandler handler, IItemInstance item, Uri templateUri)
		{
			Dictionary<string, object> data = new Dictionary<string, object>();
			TemplateItem tItem = new TemplateItem();
			using (StreamReader sw = new StreamReader(ItemBinaryReader(context, handler, item.Href, item.Version)))
			{
				tItem.Content = sw.ReadToEnd();
			}
			tItem.Item = item.ToStruct();

			data.Add("Page", tItem);

			RenderRelationshipItems(context, handler, item, data);
			return TemplateManager.Render(templateUri.AbsolutePath, data);
		}

		/// <summary>
		/// Renders the relationship items and adds them to the templateContext.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <param name="templateContext">The template context.</param>
		private static void RenderRelationshipItems(DavContext context, IRequestHandler handler, IItemInstance item, Dictionary<string, object> templateContext)
		{
			Dictionary<string, List<TemplateItem>> templateData = new Dictionary<string, List<TemplateItem>>();

			foreach (IItemRelationship itemRelationship in item.Relationships)
			{
				// if needed start a new list of items in this relationship type.
				if (!templateData.ContainsKey(itemRelationship.RelationshipType))
					templateData.Add(itemRelationship.RelationshipType, new List<TemplateItem>());
				
				var targetItem = handler.ItemRequest(context.User, RequestTypes.GetItem | (item.Published ?  RequestTypes.VersionPublished : RequestTypes.VersionLatest) , itemRelationship.TargetItem).FirstOrDefault();
				if (targetItem == null)
					continue;

				TemplateItem tItem = new TemplateItem();
				using (StreamReader sw = new StreamReader(ItemBinaryReader(context, handler, itemRelationship.TargetItem, targetItem.Version)))
				{
					tItem.Content = sw.ReadToEnd();
				}			

				tItem.Item = targetItem.ToStruct();

				templateData[itemRelationship.RelationshipType].Add(tItem);
				// TODO : we have the simple version above, the advanced version is to parse the state field and use it for order
				// and perhaps templating a related item before it is added to the page.
			}
			templateContext.Add("Related", templateData);
		}
		
		/// <summary>
		/// Returns a stream for the item binary
		/// </summary>
		/// <param name="itemUri">The item URI.</param>
		private static Stream ItemBinaryReader(DavContext context, IRequestHandler handler, Uri itemUri, int version)
		{
			Stream itemOutputStream = handler.ResourceRequest(context.User, RequestTypes.GetResource, itemUri, version);
			if (itemOutputStream == null || !itemOutputStream.CanRead)
				return Stream.Null;
			else
				return itemOutputStream;
		}

		/// <summary>
		/// Simple structure used to pair up an item and it's content for use in the template data context
		/// </summary>
		public struct TemplateItem
		{
			/// <summary>
			/// The item side of this template data element
			/// </summary>
			public ItemInstance Item;
			/// <summary>
			/// A string of content that can be output into the page and is associated with this template item.
			/// </summary>
			public string Content;
		}

		/// <summary>
		/// A structure that holds the template configuration information
		/// </summary>
		private struct TemplateMap
		{
			/// <summary>
			/// Name of the template mapping (used to specify a template via a query string)
			/// </summary>
			public string Name;
			/// <summary>
			/// MimeType of the Item to match.
			/// </summary>
			public string MimeType;
			/// <summary>
			/// MimeType to set the Response Content type to after the template is applied
			/// </summary>
			public string OutputType;
			/// <summary>
			/// ResourceProvider Id to match on item.
			/// </summary>
			public Guid ResourceProvider;
			/// <summary>
			/// Path to template.
			/// </summary>
			public Uri Path;
		}
	}
}
